package com.ruoyi.redis.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Client;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;

/**
 * Redis client base on jedis
 *
 * @author wujiyue 2015-7-10 18:34:07
 */
@Slf4j
public class InitRedisUtil {

    /**
     * redis address, like
     * "{ip}"、"{ip}:{port}"、"{redis/rediss}://域名:{password}@{ip}:{port:6379}/{db}"；Multiple
     * "," separated
     */
    private static String address;

    public static boolean initFlag = false;

    public static void init(String address) {
        InitRedisUtil.address = address;
        getInstance();
    }

    // ------------------------ ShardedJedisPool ------------------------
    /**
     * 方式01: Redis单节点 + Jedis单例 : Redis单节点压力过重, Jedis单例存在并发瓶颈 》》不可用于线上 new
     * Jedis("127.0.0.1", 6379).get("cache_key"); 方式02: Redis单节点 + JedisPool单节点连接池
     * 》》 Redis单节点压力过重，负载和容灾比较差 new JedisPool(new JedisPoolConfig(), "127.0.0.1",
     * 6379, 10000).getResource().get("cache_key"); 方式03:
     * Redis分片(通过client端集群,一致性哈希方式实现) + Jedis多节点连接池 》》Redis集群,负载和容灾较好,
     * ShardedJedisPool一致性哈希分片,读写均匀，动态扩充 new ShardedJedisPool(new JedisPoolConfig(),
     * new LinkedList<JedisShardInfo>()); 方式03: Redis集群； new
     * JedisCluster(jedisClusterNodes); // TODO
     */

    private static ShardedJedisPool shardedJedisPool;
    private static ReentrantLock INSTANCE_INIT_LOCL = new ReentrantLock(false);

    /**
     * 获取ShardedJedis实例
     *
     * @return
     */
    private static ShardedJedis getInstance() {
        if (shardedJedisPool == null) {
            try {
                if (INSTANCE_INIT_LOCL.tryLock(2, TimeUnit.SECONDS)) {

                    try {

                        if (shardedJedisPool == null) {

                            // JedisPoolConfig
                            JedisPoolConfig config = new JedisPoolConfig();
                            config.setMaxTotal(200);
                            config.setMaxIdle(50);
                            config.setMinIdle(8);
                            config.setMaxWaitMillis(10000); // 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常,
                                                            // 小于零:阻塞不确定的时间, 默认-1
                            config.setTestOnBorrow(true); // 在获取连接的时候检查有效性, 默认false
                            config.setTestOnReturn(false); // 调用returnObject方法时，是否进行有效检查
                            config.setTestWhileIdle(true); // Idle时进行连接扫描
                            config.setTimeBetweenEvictionRunsMillis(30000); // 表示idle object evitor两次扫描之间要sleep的毫秒数
                            config.setNumTestsPerEvictionRun(10); // 表示idle object evitor每次扫描的最多的对象数
                            config.setMinEvictableIdleTimeMillis(60000); // 表示一个对象至少停留在idle状态的最短时间，然后才能被idle object
                                                                         // evitor扫描并驱逐；这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义

                            // JedisShardInfo List
                            List<JedisShardInfo> jedisShardInfos = new LinkedList<JedisShardInfo>();

                            String[] addressArr = address.split(",");
                            for (int i = 0; i < addressArr.length; i++) {
                                JedisShardInfo jedisShardInfo = new JedisShardInfo(addressArr[i]);
                                jedisShardInfos.add(jedisShardInfo);
                            }
                            shardedJedisPool = new ShardedJedisPool(config, jedisShardInfos);
                            log.info("=======  RedisUtil.ShardedJedisPool Init Success!  =======");
                        }

                    } finally {
                        INSTANCE_INIT_LOCL.unlock();
                        initFlag = true;
                    }
                }

            } catch (InterruptedException e) {
                log.error(e.getMessage(), e);
            }
        }

        if (shardedJedisPool == null) {
            initFlag = false;
            throw new NullPointerException("RedisUtil.ShardedJedisPool is null.");
        }

        ShardedJedis shardedJedis = shardedJedisPool.getResource();
        return shardedJedis;
    }

    public static void close() throws IOException {
        if (shardedJedisPool != null) {
            shardedJedisPool.close();
        }
    }

    // ------------------------ serialize and unserialize ------------------------

    /**
     * 将对象-->byte[] (由于jedis中不支持直接存储object所以转换成byte[]存入)
     *
     * @param object
     * @return
     */
    protected static byte[] serialize(Object object) {
        ObjectOutputStream oos = null;
        ByteArrayOutputStream baos = null;
        try {
            // 序列化
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            oos.writeObject(object);
            byte[] bytes = baos.toByteArray();
            return bytes;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            try {
                oos.close();
                baos.close();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
        return null;
    }

    /**
     * 将byte[] -->Object
     *
     * @param bytes
     * @return
     */
    private static Object unserialize(byte[] bytes) {
        ByteArrayInputStream bais = null;
        try {
            // 反序列化
            bais = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(bais);
            return ois.readObject();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            try {
                bais.close();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
        return null;
    }

    // ------------------------ jedis util ------------------------
    /**
     * 存储简单的字符串或者是Object 因为jedis没有分装直接存储Object的方法，所以在存储对象需斟酌下
     * 存储对象的字段是不是非常多而且是不是每个字段都用到，如果是的话那建议直接存储对象，
     * 否则建议用集合的方式存储，因为redis可以针对集合进行日常的操作很方便而且还可以节省空间
     */
    /**
     * Set String
     *
     * @param key
     * @param value
     * @return
     */
    public static String setStringValue(String key, String value) {
        String result = null;
        ShardedJedis client = getInstance();
        try {
            result = client.set(key, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return result;
    }

    public static String type(String key) {
        String result = null;
        ShardedJedis client = getInstance();
        try {
            result = client.type(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return result;
    }

    public static long ttl(String key) {
        long result = 0L;
        ShardedJedis client = getInstance();
        try {
            result = client.ttl(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return result;
    }

    /**
     * Set String
     *
     * @param key
     * @param value
     * @param seconds 存活时间,单位/秒
     * @return
     */
    public static String setStringValue(String key, String value, int seconds) {
        String result = null;
        ShardedJedis client = getInstance();
        try {
            result = client.setex(key, seconds, value);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return result;
    }

    /**
     * Set Object
     *
     * @param key
     * @param obj
     * @param seconds 存活时间,单位/秒
     */
    public static String setObjectValue(String key, Object obj, int seconds) {
        String result = null;
        ShardedJedis client = getInstance();
        try {
            // result = client.setex(key.getBytes(), seconds, serialize(obj));
            result = client.setex(key, seconds, JSON.toJSONString(obj));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return result;
    }

    /**
     * Get String
     *
     * @param key
     * @return
     */
    public static String getStringValue(String key) {
        String value = null;
        ShardedJedis client = getInstance();
        try {
            value = client.get(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return value;
    }

    /**
     * Get Object
     *
     * @param key
     * @return
     */
    public static Object getObjectValue(String key) {
        Object obj = null;
        ShardedJedis client = getInstance();
        try {
            byte[] bytes = client.get(key.getBytes());
            if (bytes != null && bytes.length > 0) {
                obj = unserialize(bytes);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return obj;
    }

    public static Object getObjectValue(String key, Class<?> cls) {
        Object obj = null;
        ShardedJedis client = getInstance();
        try {
            /*
             * byte[] bytes = client.get(key.getBytes()); if (bytes != null && bytes.length
             * > 0) { obj = unserialize(bytes); }
             */
            ;
            obj = JSONObject.parseObject(client.get(key), cls);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return obj;
    }

    /**
     * Delete key
     *
     * @param key
     * @return Integer reply, specifically: an integer greater than 0 if one or more
     *         keys were removed 0 if none of the specified key existed
     */
    public static Long del(String key) {
        Long result = null;
        ShardedJedis client = getInstance();
        try {
            result = client.del(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return result;
    }

    /**
     * incrBy i(+i)
     *
     * @param key
     * @param i
     * @return new value after incr
     */
    public static Long incrBy(String key, int i) {
        Long result = null;
        ShardedJedis client = getInstance();
        try {
            result = client.incrBy(key, i);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return result;
    }

    /**
     * exists valid
     *
     * @param key
     * @return Boolean reply, true if the key exists, otherwise false
     */
    public static boolean exists(String key) {
        Boolean result = null;
        ShardedJedis client = getInstance();
        try {
            result = client.exists(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return result;
    }

    /**
     * expire reset
     *
     * @param key
     * @param seconds 存活时间,单位/秒
     * @return Integer reply, specifically: 1: the timeout was set. 0: the timeout
     *         was not set since the key already has an associated timeout (versions
     *         lt 2.1.3), or the key does not exist.
     */
    public static long expire(String key, int seconds) {
        Long result = null;
        ShardedJedis client = getInstance();
        try {
            result = client.expire(key, seconds);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return result;
    }

    /**
     * expire at unixTime
     *
     * @param key
     * @param unixTime
     * @return
     */
    public static long expireAt(String key, long unixTime) {
        Long result = null;
        ShardedJedis client = getInstance();
        try {
            result = client.expireAt(key, unixTime);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return result;
    }

    /**
     * 实现redis keys 模糊查询
     * 
     * @author hq
     * @param pattern
     * @return
     */
    public static List<String> keys(String pattern) {
//        log.debug("====  Redis: 开始模糊查询【{}】keys  ====", pattern);
        ShardedJedis shardedJedis = shardedJedisPool.getResource();
        Collection<Jedis> allShards = shardedJedis.getAllShards();
        List<String> keyList = new ArrayList<>();
        for (Jedis jedis : allShards) {
            Set<String> keys = jedis.keys(pattern);
            keyList.addAll(keys);
        }
//        log.debug("====  Redis: 结束模糊查询  ====");
        return keyList;
    }

    public static void main(String[] args) {
        // String SsoRedisAddress = "redis://域名:password@127.0.0.1:6379/0";
        String SsoRedisAddress = "redis://127.0.0.1:6379/0";
        init(SsoRedisAddress);
        ShardedJedis client = getInstance();
        client.set("cnblog", "cnblog");
        client.set("redis", "redis");
        client.set("test", "test");
        client.set("123456", "1234567");
        Client client1 = client.getShard("cnblog").getClient();
        Client client2 = client.getShard("redis").getClient();
        Client client3 = client.getShard("test").getClient();
        Client client4 = client.getShard("123456").getClient();

        //// 打印key在哪个server中
        System.out.println("cnblog in server:" + client1.getHost() + " and port is:" + client1.getPort());
        System.out.println("redis  in server:" + client2.getHost() + " and port is:" + client2.getPort());
        System.out.println("test   in server:" + client3.getHost() + " and port is:" + client3.getPort());
        System.out.println("123456 in server:" + client4.getHost() + " and port is:" + client4.getPort());

        List<String> keys = keys("*session*");
        keys.forEach(s -> {
            System.out.println(s);
        });
    }

}
